package org.apache.ocean.main.search;

import org.apache.lucene.util.PriorityQueue;
import org.apache.ocean.main.IndexSchema;
import org.apache.ocean.main.Index.IndexSnapshot;
import org.apache.ocean.main.fields.FieldType;
import org.apache.ocean.main.fields.SchemaField;
import org.apache.ocean.main.fields.SchemaField.Attribute;
import org.apache.ocean.main.search.Search.FieldDoc;
import org.apache.ocean.main.search.Search.ScoreDoc;
import org.apache.ocean.main.search.Search.Sort;
import org.apache.ocean.main.search.cache.FieldCacheKey.FieldIndex;
import org.apache.ocean.main.util.Constants;

// TODO: remove scorepriorityqueue, not useful
public final class FieldDocSortQueue extends PriorityQueue {
	private Sort sort;
	private DocComparator[] docComparators;

	// private int distanceIndex = -1;

	public FieldDocSortQueue(Sort sort, int size, IndexSnapshot indexSnapshot) throws Exception {
		this.sort = sort;
		initialize(size);
		IndexSchema.Category indexSchemaCategory = indexSnapshot.getIndexSchemaCategory();
		docComparators = new DocComparator[sort.size()];
		for (int x = 0; x < sort.size(); x++) {
			Sort.Field field = sort.get(x);
			if (field.getName().equals(Constants.SCORE)) {
				docComparators[x] = new ScoreDocComparator();
			} else if (field instanceof Sort.Distance) {
				docComparators[x] = new DistanceDocComparator();
			} else {
				String name = field.getName();
				SchemaField schemaField = indexSchemaCategory.getField(name);
				FieldType fieldType = schemaField.getFieldType();
				FieldIndex fieldIndex = indexSnapshot.getFieldIndex(name);
				if (schemaField.has(Attribute.SORTMISSINGLAST)) {
					docComparators[x] = new FieldIndexDocComparatorNullLast(fieldIndex);
				} else {
					docComparators[x] = new FieldIndexDocComparator(fieldIndex);
				}
			}
		}
	}

	FieldDoc fillFields(final FieldDoc fieldDoc) {
		final int n = docComparators.length;
		final Comparable[] comparables = new Comparable[n];
		for (int i = 0; i < n; ++i)
			comparables[i] = docComparators[i].getComparable(fieldDoc);
		fieldDoc.comparables = comparables;
		return fieldDoc;
	}

	// public int getDistanceIndex() {
	// return distanceIndex;
	// }

	public static class DistanceDocComparator implements DocComparator {
		public Comparable getComparable(ScoreDoc scoreDoc) {
			return scoreDoc.score;
		}

		public int compare(ScoreDoc i, ScoreDoc j) {
			return i.distance.compareTo(j.distance);
		}
	}

	public static class ScoreDocComparator implements DocComparator {
		public Comparable getComparable(ScoreDoc scoreDoc) {
			return scoreDoc.score;
		}

		public int compare(ScoreDoc i, ScoreDoc j) {
			if (i.score > j.score)
				return -1;
			if (i.score < j.score)
				return 1;
			if (i.doc > j.doc) return -1;
			else return 1;
		}
	}

	public static class FieldIndexDocComparator implements DocComparator {
		protected FieldIndex fieldIndex;

		public FieldIndexDocComparator(FieldIndex fieldIndex) {
			this.fieldIndex = fieldIndex;
		}

		public Comparable getComparable(ScoreDoc scoreDoc) {
			return fieldIndex.getComparable(scoreDoc.doc);
		}

		public int compare(ScoreDoc i, ScoreDoc j) {
			final int fi = fieldIndex.getOrder(i.doc);
			final int fj = fieldIndex.getOrder(j.doc);
			if (fi < fj)
				return -1;
			if (fi > fj)
				return 1;
			return 0;
		}
	}

	public static class FieldIndexDocComparatorNullLast extends FieldIndexDocComparator {
		public static final String missingValueProxy = "\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffffNULL_VAL";

		public FieldIndexDocComparatorNullLast(FieldIndex fieldIndex) {
			super(fieldIndex);
		}

		public Comparable getComparable(int doc) {
		  int order = fieldIndex.getOrder(doc);
		  return (0 == order) ? missingValueProxy : fieldIndex.getComparable(doc);
		}

		public final int compare(final ScoreDoc i, final ScoreDoc j) {
			final int fi = fieldIndex.getOrder(i.doc);
			final int fj = fieldIndex.getOrder(j.doc);
			if (fi == fj)
				return 0;
			if (fi == 0)
				return 1;
			if (fj == 0)
				return -1;
			return fi < fj ? -1 : 1;
		}
	}

	protected final boolean lessThan(final Object a, final Object b) {
		final FieldDoc docA = (FieldDoc) a;
		final FieldDoc docB = (FieldDoc) b;
		final int n = docComparators.length;
		int c = 0;
		for (int i = 0; i < n && c == 0; ++i) {
			boolean reverse = sort.get(i).isReverse();
			if (reverse) {
				c = docComparators[i].compare(docB, docA);
			} else {
				c = docComparators[i].compare(docA, docB);
			}
		}
		// avoid random sort order that could lead to duplicates (bug #31241):
		if (c == 0)
			return docA.doc > docB.doc;
		return c > 0;
	}

	public interface DocComparator {
		public int compare(ScoreDoc i, ScoreDoc j);
		public Comparable getComparable(ScoreDoc scoreDoc);
	}
}
